﻿using System;
using System.Threading;

namespace QQ2564874169.RelationalSql
{
    public static class LongId
    {
        private static int _appid;
        private static int _seqlen = -1;
        private static long _start;
        private static int _index;
        private static int _max;
        private const int _msec = 10000;

        public static int AppId
        {
            get
            {
                if (_appid < 1)
                {
                    Interlocked.CompareExchange(ref _appid, GetPid(), 0);
                }
                return _appid;
            }
            set
            {
                if (_appid > 0)
                {
                    throw new ArgumentException("AppId已经初始化。");
                }
                if (value > 999)
                {
                    throw new ArgumentException("AppId的范围必须是大于等于0并且小于1000。");
                }
                _appid = value;
            }
        }

        public static int Seqlength
        {
            get { return _seqlen; }
            set
            {
                if (value < 1 || value > 7)
                    throw new ArgumentException("一秒钟内生成的序列的位数，范围1到7位数。");
                _seqlen = value;
                _max = (int)Math.Pow(10, _seqlen);
            }
        }

        static LongId()
        {
            Seqlength = 5;
            _start = DateTime.Parse("2016-1-1").ToUniversalTime().Ticks / 10000000;
        }

        private static int GetPid()
        {
            var pid = Guid.NewGuid().GetHashCode();
            if (pid < 0)
                pid = pid * -1;
            if (pid > 999)
                pid = pid % 100;
            return pid;
        }

        public static long New()
        {
            int seq;
            do
            {
                seq = Interlocked.Increment(ref _index);

                if (seq < _max) break;

                var curr = DateTime.UtcNow;
                var time = curr.Ticks + (1000 - (int)(curr.Ticks / _msec % 1000)) * _msec;
                var wait = new SpinWait();
                while (DateTime.UtcNow.Ticks < time)
                {
                    wait.SpinOnce();
                }

                if (seq == _max)
                {
                    seq = _index = 0;
                    break;
                }

            } while (true);

            var sec = DateTime.UtcNow.Ticks / 10000000;
            var num = (sec - _start) * 1000 + AppId;
            return num * _max + seq;
        }
    }
}
